#!/usr/bin/python2.5
#
# (c) 2009 - Chip Turner
#
# Apache License, blah blah, it's a few hundred lines of python.

"""
There are two primary modes for %prog:

%prog -r REALM -p PLAYER

%prog -r REALM -g GUILD -a ACHIEVEMENT

The first mode prints all achievements for a player, completed or not.
The second mode is far more interesting.  It is designed to help
determine what sub-achievements are needed by guild members to reach
the specified ACHIEVEMENT.  It walks the guild roster (stopping at the
specified numeric maxrank), checking criteria for each member.  It
filters by level (default, only level 80) and allows multiple players
to be excluded via -x options, such as alts.
"""

import collections
import logging
import os
import time
import sys
import optparse
import textwrap

import armory_lib

def main(args):
  parser = optparse.OptionParser(__doc__)
  parser.add_option("-r", "--realm", help="realm player or guild is on")
  parser.add_option("", "--eu", help="Use EU Armory instead of US armory",
                    action="store_true")
  player_group = optparse.OptionGroup(parser, "Player Options")
  player_group.add_option("-p", "--player", help="player name to extract data for")
  player_group.add_option("", "--completed", help="only display completed achievements",
                          action="store_true")
  parser.add_option("-v", "--verbose", help="show debugging output", action="store_true")
  parser.add_option_group(player_group)

  guild_group = optparse.OptionGroup(parser, "Guild Options")
  guild_group.add_option("-g", "--guild", help="guild name to extract data for")
  guild_group.add_option("-a", "--achievement", help="achievement to detail")
  guild_group.add_option("-m", "--maxrank", help="maximum rank to show results for (numeric, gm=0)",
                         type=int)
  guild_group.add_option("-l", "--level", help="minimum level to show results for", type=int, default=80)
  guild_group.add_option("-x", "--exclude", help="exclude this user", action="append")
  guild_group.add_option("", "--exclude-file", help="path to file containing names to exclude, one per line")
  parser.add_option_group(guild_group)

  options, args = parser.parse_args(args)

  if options.verbose:
    armory_lib.InitLogging(logging.DEBUG)
  else:
    armory_lib.InitLogging()

  if options.eu:
    armory_interface = armory_lib.ArmoryInterface("eu")
  else:
    armory_interface = armory_lib.ArmoryInterface("us")

  if options.player:
    categories = armory_interface.ParseCategories(options.realm, options.player)
    for cat_name, cat_id in categories:
      print "%s:" % cat_name
      achievements = armory_interface.ParseAchievements(options.realm, options.player, cat_id)
      for achievement in achievements:
        if options.completed and not achievement.completed: continue
        
        if achievement.completed:
          print "  %s (%s)" % (achievement.name, achievement.completed)
        else:
          print "  %s (not done)" % achievement.name
  elif options.guild and options.achievement:
    print "Report generated: %s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
    print

    exclude = options.exclude or []
    if options.exclude_file:
      exclude.extend(l.strip() for l in open(options.exclude_file, "r").readlines())

    categories, achievement_id = None, None
    cat_id = None
    still_needed = collections.defaultdict(set)   # achievement_name -> [ player player ... ]
    members = armory_interface.ParseRoster(options.realm, options.guild)
    matching_members = 0
    print "By Player:"
    for name, level, race_id, class_id, rank in members:
      if level < options.level: continue
      if options.maxrank and rank > options.maxrank: continue
      if name in exclude: continue
      matching_members += 1
      still_needed[options.achievement].add(name)

      if not categories:
        categories = armory_interface.ParseCategories(options.realm, name)

      if not cat_id:
        for tmp_cat_name, tmp_cat_id in categories:
          achievements = armory_interface.ParseAchievements(options.realm, name, tmp_cat_id)
          for achievement in achievements:
            if achievement.name == options.achievement:
              cat_id = tmp_cat_id
              break
      if not cat_id:
        print >>sys.stderr, "Unable to find category for %s" % options.achievement
        sys.exit(1)

      achievements = armory_interface.ParseAchievements(options.realm, name, cat_id)
      print "%s (Level %s Rank %d):" % (name, level, rank)
      for achievement in achievements:
        if achievement.name == options.achievement:
          still_needed[options.achievement].discard(name)
          criteria = achievement.GetCriteria()
          completed = 0
          all_completed = False
          for c, date in criteria:
            if date: completed += 1
          if achievement.completed:
            completed = len(criteria)
          if completed == len(criteria):
            # stupid armory sometimes doesn't list dates on completed
            # achievements' criteria yet the achievement is complete.
            # argh.
            all_completed = True
          print "  %s (%d/%d):" % (achievement.name, completed, len(criteria))
          for criteria_name, completed in criteria:
            if completed or all_completed:
              print "    [DONE] %s" % (criteria_name)
            else:
              print "    [    ] %s" % criteria_name
              still_needed[criteria_name].add(name)

    print
    print "By Needed Achievement:"
    wrapper = textwrap.TextWrapper(initial_indent='  ', subsequent_indent='  ')
    for name, players in sorted(still_needed.items()):
      if name == options.achievement: continue
      print "%s (%d/%d)" % (name, len(players), matching_members)
      players = sorted(players)
      print "\n".join(wrapper.wrap(", ".join(players)))
          
  else:
    parser.print_help()

if __name__ == '__main__':
  main(sys.argv[1:])
